(ns js
  (:use clojure.java.io)
  (:use util.utils)
  (:require clojure.string))

;(set! *warn-on-reflection* true)

(defn- engin-manager []
  (javax.script.ScriptEngineManager.))

(def ^:dynamic *js-engine-manager* (delay (engin-manager))) ; XXX: is ScriptEngineManager thread safe?

(defn- js-engine-by-manager
  ([^javax.script.ScriptEngineManager engine-manager]
    (.getEngineByName engine-manager "js"))
  ([]
    (js-engine-by-manager @*js-engine-manager*)))

(defn rhino-engine []
  (com.sun.script.javascript.RhinoScriptEngine.)) ; XXX: can I use this internal class directly and safely?

(defn ^javax.script.ScriptEngine js-engine []
;  (js-engine-by-manager)
  (rhino-engine))

(defn eval-js-file
  ([^javax.script.ScriptEngine engine path]
    (with-open [file (java.io.FileReader. (as-file path))]
      (.eval engine file)))
  ([path]
    (eval-js-file (js-engine) path)))

(defn eval-js [^String s & args]
  (let [args (parse-arguments args :imports :sources :vars :scripts)
        engine (js-engine)]
    (doseq [path (map #(str (clojure.string/replace % #"\." "/") ".js") (:imports args))]
      (eval-js-file engine (get-resource path)))
    (doseq [path (:sources args)]
      (eval-js-file engine path))
    (doseq [[n v] (:vars args)]
      (.put engine n v))
    (doseq [script (:scripts args)]
      (.eval engine ^String script))
    (.eval engine s)))


(def ^String js-to-java "
function dump(obj) {
  if (typeof(obj) == 'object' && !obj.length) {
    var javao = new java.util.HashMap();
    for (var k in obj) {
      var v = obj[k];
      javao.put(k, dump(v))
    }
    return javao;
  }
  else if (typeof(obj) == 'object' && obj.length) {
    var javao = new java.util.ArrayList();
    for (var i = 0; i < obj.length; i++) {
      javao.add(dump(obj[i]));
    }
    return javao;
  }
  else {
    return obj;
  }

}
")

(defn- js-value-to-java-value [jsvalue]
  (let [engine (js-engine)]
    (.eval engine js-to-java)
    (.put engine "x" jsvalue)
    (.eval engine "dump(x)")))

(defn- java-value-to-clojure-value [javao]
  (cond
    (instance? java.util.HashMap javao) (apply array-map
                                          (map java-value-to-clojure-value
                                            (apply concat (into {} javao))))
    (instance? java.util.ArrayList javao) (vec (map java-value-to-clojure-value javao))
    :else javao))

(defn from-js [js-object]
  (java-value-to-clojure-value
    (js-value-to-java-value
      js-object)))

;; XXX: a sort of stupid...
(defn eval-json [s]
  (from-js (eval-js (str "(" s ")"))))


